package tech.waterism.algorithm.moea.POA;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import tech.waterism.algorithm.moea.PSO.CheckUtil;
import tech.waterism.algorithm.moea.PSO.CommonFitness;
import tech.waterism.dto.*;
import tech.waterism.enums.FieldEnum;
import tech.waterism.enums.LogicEnum;

import java.util.*;
import java.util.stream.Collectors;

@Data
@Slf4j
public class DispatchFitnessPOA {

    private static final double AEQ_LIMIT = 0.01;
    private final TopoDataDto topoDataDto;
    private final AlgorithmDispatch algorithm;
    // varieble reservoir's qout
    private final double[][] paras;
    private List<String> message;

    public DispatchFitnessPOA(TopoDataDto topoDataDto, AlgorithmDispatch algorithm, double[][] paras) {
        this.topoDataDto = topoDataDto;
        this.algorithm = algorithm;
        this.paras = paras;
        this.message = new LinkedList<>();
        Map<String, TopoObjectDto> topoObjectDtoMap = topoDataDto.getTopoObject().stream()
                .collect(Collectors.toMap(i -> i.getFpIndex().getStcd(), v -> v, (k1, k2) -> k1));
        for (DispatchConstraint constrain : algorithm.getConstrains()) {
            String stcd = constrain.getStcd();
            if (topoObjectDtoMap.containsKey(stcd)) {
                constrain.setStnm(topoObjectDtoMap.get(stcd).getFpIndex().getStnm());
            }
        }
    }

    public double[] getFitness() {
        double[] result = new double[algorithm.getObjectives().size()];
        try {
            Map<String, TopoObjectDto> topoObjectDtoMap = topoDataDto.getTopoObject().stream()
                    .collect(Collectors.toMap(i -> i.getFpIndex().getStcd(), v -> v, (k1, k2) -> k1));
            for (int i = 0; i < algorithm.getObjectives().size(); i++) {
                DispatchObjective objective = algorithm.getObjectives().get(i);
                if (topoObjectDtoMap.containsKey(objective.getStcd())) {
                    result[i] = Optional.ofNullable(CheckUtil
                            .calFitness(topoObjectDtoMap.get(objective.getStcd()).getFpIndex(), topoDataDto.getFymdh(),
                                    objective)).orElseThrow(() -> new Exception("计算目标值失败"));
                }
            }

            PunishDto punishDto = punish();
            this.message = punishDto.getMessage();
            double punish = punishDto.getPunish();
            for (int i = 0; i < result.length; i++) {
                result[i] = result[i] + result[i] * 1e4 * punish;
            }
            log.debug("punish:" + punish);
            // 归一化
            for (int i = 0; i < algorithm.getObjectives().size(); i++) {
                result[i] = result[i] * algorithm.getObjectives().get(i).getWeight();
            }
            return result;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            this.message = Arrays.asList(e.getMessage());
            return null;
        }
    }

    /**
     * get punish value by constraints
     */
    private PunishDto punish() throws Exception {
        List<DispatchConstraint> constrains = algorithm.getConstrains();
        if (constrains == null || constrains.size() == 0) {
            return null;
        }
        PunishDto result = new PunishDto();
        List<String> message = new LinkedList<>();
        Date fymdh = topoDataDto.getFymdh();
        Map<String, TopoObjectDto> topoObjectDtoMap = topoDataDto.getTopoObject().stream()
                .collect(Collectors.toMap(i -> i.getFpIndex().getStcd(), v -> v, (k1, k2) -> k1));
        double checkResult = 0;
        for (DispatchConstraint constrain : constrains) {
            String stcd = constrain.getStcd();
            String stnm = constrain.getStnm();
            double checkResultItem = 0;
            if (topoObjectDtoMap.containsKey(stcd)) {
                FPIndexDto fpIndexDto = topoObjectDtoMap.get(stcd).getFpIndex();
                LogFieldDto logFieldDto = new LogFieldDto().setStcd(stcd).setStnm(stnm);
                List<ForecastDataDto> data = fpIndexDto.getData();
                List<ForecastDataDto> checkData =
                        data.stream().filter(datum -> datum.getDt().after(fymdh)).collect(Collectors.toList());
                if (constrain.getCompareField().equals(FieldEnum.Z_END)) {
                    checkResultItem = CommonFitness
                            .calFieldZend(logFieldDto, checkData.get(checkData.size() - 1), constrain,
                                    fpIndexDto.getFloodCharacter()) * 1e6;
                } else if (constrain.getCompareField().equals(FieldEnum.RZ)) {
                    checkResultItem =
                            CommonFitness.calField(logFieldDto, checkData, constrain, fpIndexDto.getFloodCharacter()) *
                                    1e4;
                } else {
                    checkResultItem =
                            CommonFitness.calField(logFieldDto, checkData, constrain, fpIndexDto.getFloodCharacter());
                }
            }
            if (checkResultItem > 0) {
                message.add(stnm + "(" + stcd + ")" + constrain.getCompareField().getName() +
                        constrain.getLogic().getName() + "未满足约束");
                checkResult += checkResultItem;
            }
        }
        result.setPunish(checkResult);
        result.setMessage(message);
        return result;
    }

    public CheckConstrainDto checkConstrain(Boolean pre) throws Exception {
        CheckConstrainDto result = new CheckConstrainDto();
        List<DispatchConstraint> constrains = algorithm.getConstrains();
        if (constrains == null || constrains.size() == 0) {
            result.setFlg(true);
            return result;
        }
        Date fymdh = topoDataDto.getFymdh();
        Map<String, List<DispatchConstraint>> constrainMap =
                constrains.stream().collect(Collectors.groupingBy(DispatchConstraint::getStcd));
        for (TopoObjectDto topoObjectDto : topoDataDto.getTopoObject()) {
            String stcd = topoObjectDto.getFpIndex().getStcd();
            if (constrainMap.containsKey(stcd)) {
                List<DispatchConstraint> constraintList = constrainMap.get(stcd);
                LogFieldDto logFieldDto = new LogFieldDto().setStcd(stcd).setStnm(topoObjectDto.getFpIndex().getStnm());
                List<ForecastDataDto> data = topoObjectDto.getFpIndex().getData();
                List<ForecastDataDto> checkData =
                        data.stream().filter(datum -> datum.getDt().after(fymdh)).collect(Collectors.toList());
                for (DispatchConstraint constrain : constraintList) {
                    if (Boolean.TRUE.equals(pre)) {
                        // 可执行判断，只检查河道流量上限、水位上限和水库入流上限约束
                        if (constrain.getCompareField().equals(FieldEnum.QSIM) ||
                                constrain.getCompareField().equals(FieldEnum.ZSIM) ||
                                constrain.getCompareField().equals(FieldEnum.QIN)) {
                            if (constrain.getLogic() == LogicEnum.LTE || constrain.getLogic() == LogicEnum.LT) {
                                boolean checkResult = CommonFitness.checkField(logFieldDto, checkData, constrain,
                                        topoObjectDto.getFpIndex().getFloodCharacter());
                                if (!checkResult) {
                                    result.setFlg(false);
                                    String message = constrain.getStnm() + "(" + constrain.getStcd() + ")" +
                                            constrain.getCompareField().getName() + constrain.getLogic().getName() +
                                            "未满足约束";
                                    result.setMessage(message);
                                    return result;
                                }
                            }
                        }
                    } else {
                        if (constrain.getCompareField().equals(FieldEnum.Z_END)) {
                            double punish = CommonFitness.calFieldZend(logFieldDto, checkData.get(checkData.size() - 1), constrain,
                                    topoObjectDto.getFpIndex().getFloodCharacter());
                            if (punish > AEQ_LIMIT) {
                                result.setFlg(false);
                                String message = constrain.getStnm() + "(" + constrain.getStcd() + ")" +
                                        constrain.getCompareField().getName() + constrain.getLogic().getName() +
                                        "未满足约束" + ",差值为" + punish;
                                result.setMessage(message);
                                return result;
                            }
                        } else if (!constrain.getCompareField().equals(FieldEnum.QOUT_DIFF)) {
                            boolean checkResult = CommonFitness.checkField(logFieldDto, checkData, constrain,
                                    topoObjectDto.getFpIndex().getFloodCharacter());
                            if (!checkResult) {
                                result.setFlg(false);
                                String message = constrain.getStnm() + "(" + constrain.getStcd() + ")" +
                                        constrain.getCompareField().getName() + constrain.getLogic().getName() +
                                        "未满足约束";
                                result.setMessage(message);
                                return result;
                            }
                        }
                    }
                }
            }
        }
        result.setFlg(true);
        return result;
    }
}

